package com.soul.utils;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.Args;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.util.ResourceUtils;

import javax.net.ssl.SSLContext;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.security.KeyStore;

public class HttpsInvokeUtil {
    private static final Logger logger = LogManager.getLogger(HttpsInvokeUtil.class);
    private static final String KEYSTORE_TYPE = "PKCS12";
    private static final String TLS = "TLSv1";
    static RequestConfig requestConfig = RequestConfig.custom()
            .setConnectionRequestTimeout(5000)
            .setSocketTimeout(5000)
            .setConnectTimeout(5000).build();
    /**
     * 证书路径，默认使用的.p12文件,只能用绝对路径
     */
    private String certPath;

    /**
     * 证书密码
     */
    private String certPwd;

    /**
     * 是否需要认证证书
     */
    private boolean isVerifyCert;

    /**
     * 同一个服务请求的证书缓存在内存里
     */
    private KeyStore keyStore;

    /**
     * 是否进行代理
     */
    private boolean isProxy;

    /**
     * 代理服务端口
     */
    private int proxyPort;

    /**
     * 代理服务的hostName
     */
    private String proxyHostName;

    /**
     * 获取一个信任任何证书的https请求
     */
    public static HttpsInvokeUtil getInstance(String intfType) {
        return getInstance(false, false, intfType, requestConfig, null, null, null, 0);
    }

    private static HttpsInvokeUtil getInstance(boolean isVerifyCert, boolean isProxy, String intfType, RequestConfig requestConfig, String certPath, String certPwd, String proxyHostName, int proxyPort) {
        HttpsInvokeUtil httpsInvokeUtil = new HttpsInvokeUtil(null, isVerifyCert, isProxy, certPath, certPwd, proxyHostName, proxyPort);
        return httpsInvokeUtil;
    }

    private HttpsInvokeUtil(RequestConfig requestConfig, boolean isVerifyCert, boolean isProxy, String certPath, String certPwd, String proxyHostName, int proxyPort) {
        if (null != requestConfig) {
            this.requestConfig = requestConfig;
        }
        this.isVerifyCert = isVerifyCert;
        this.isProxy = isProxy;
        this.certPath = certPath;
        this.certPwd = certPwd;
        this.proxyHostName = proxyHostName;
        this.proxyPort = proxyPort;

        //如果是认证证书的就校验一下证书
        if (isVerifyCert) {
            verifyCert();
            initKeyStore();
        }
    }

    public CloseableHttpClient getHttpsClient() {
        if (isVerifyCert) {
            return getHttpClientWithCert();
        } else {
            return buildSSLCloseableHttpClient();
        }
    }

    private CloseableHttpClient getHttpClientWithCert() {
        try {
            SSLContext sslContext = SSLContexts.custom()
                    .useProtocol(TLS)
                    .loadTrustMaterial((certificate, authType) -> true)
                    .loadKeyMaterial(keyStore, certPwd.toCharArray())
                    .build();

            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new DefaultHostnameVerifier());

            PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(
                    RegistryBuilder.<ConnectionSocketFactory>create()
                            .register("http", PlainConnectionSocketFactory.getSocketFactory())
                            .register("https", sslsf)
                            .build());
            connManager.setMaxTotal(300);
            connManager.setDefaultMaxPerRoute(300);
            connManager.setValidateAfterInactivity(3000);
            ConnectionKeepAliveStrategy connectionKeepAliveStrategy = (final HttpResponse response, final HttpContext context) -> {
                Args.notNull(response, "HTTP response");
                final HeaderElementIterator it = new BasicHeaderElementIterator(
                        response.headerIterator(HTTP.CONN_KEEP_ALIVE));
                while (it.hasNext()) {
                    final HeaderElement he = it.nextElement();
                    final String param = he.getName();
                    final String value = he.getValue();
                    if (value != null && param.equalsIgnoreCase("timeout")) {
                        try {
                            return Long.parseLong(value) * 1000;
                        } catch (final NumberFormatException ignore) {
                        }
                    }
                }
                return 3 * 1000;
            };
            return HttpClients.custom().useSystemProperties()
                    .setConnectionManager(connManager)
                    .setKeepAliveStrategy(connectionKeepAliveStrategy)
                    .disableAutomaticRetries()
                    .build();
        } catch (Exception e) {
            logger.error("创建一个带证书校验的httpclient出现exception", e);
        }

        return HttpClients.createDefault();
    }


    /**
     * 创建SSL安全连接
     */
    private CloseableHttpClient buildSSLCloseableHttpClient() {
        try {
            SSLContext sslContext = SSLContexts.custom()
                    .loadTrustMaterial(null, (chain, authType) -> true)
                    .useProtocol(TLS)
                    .build();

            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());

            PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(
                    RegistryBuilder.<ConnectionSocketFactory>create()
                            .register("http", PlainConnectionSocketFactory.getSocketFactory())
                            .register("https", sslsf)
                            .build());
            connManager.setMaxTotal(300);
            connManager.setDefaultMaxPerRoute(30);
            connManager.setValidateAfterInactivity(3000);
            ConnectionKeepAliveStrategy connectionKeepAliveStrategy = (final HttpResponse response, final HttpContext context) -> {
                Args.notNull(response, "HTTP response");
                final HeaderElementIterator it = new BasicHeaderElementIterator(
                        response.headerIterator(HTTP.CONN_KEEP_ALIVE));
                while (it.hasNext()) {
                    final HeaderElement he = it.nextElement();
                    final String param = he.getName();
                    final String value = he.getValue();
                    if (value != null && param.equalsIgnoreCase("timeout")) {
                        try {
                            return Long.parseLong(value) * 1000;
                        } catch (final NumberFormatException ignore) {
                        }
                    }
                }
                return 3 * 1000;
            };
            if (isProxy) {
                HttpHost proxy = new HttpHost(proxyHostName, proxyPort);
                DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);

                return HttpClients.custom().useSystemProperties()
                        .setKeepAliveStrategy(connectionKeepAliveStrategy)
                        .disableAutomaticRetries()
                        .setConnectionManager(connManager).setSSLSocketFactory(sslsf).setRoutePlanner(routePlanner).build();
            }

            return HttpClients.custom().useSystemProperties()
                    .setKeepAliveStrategy(connectionKeepAliveStrategy)
                    .disableAutomaticRetries()
                    .setConnectionManager(connManager).setSSLSocketFactory(sslsf).build();
        } catch (Exception e) {
            logger.error("buildSSLCloseableHttpClient error", e);
        }
        return HttpClients.createDefault();
    }

    private void verifyCert() {
        //校验certPath是否可以正常读取
        if (StringUtils.isBlank(certPath)) {
            throw new IllegalArgumentException("certPath not be blank.");
        }

        //校验certPath的证书路径是否正常，可获得证书
        try {
            if (!ResourceUtils.getFile(certPath).isFile()) {
                throw new IllegalArgumentException("can not get cert from certPath:" + certPath);
            }
        } catch (FileNotFoundException e) {
            throw new IllegalArgumentException("can not find cert from certPath:" + certPath, e);
        }
    }

    private void initKeyStore() {
        try {
            keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
            keyStore.load(new FileInputStream(ResourceUtils.getFile(certPath)), certPwd.toCharArray());
        } catch (Exception e) {
            throw new IllegalArgumentException("can not init keyStore from certPath:" + certPath, e);
        }
    }


    public String getCertPath() {
        return certPath;
    }

    public String getCertPwd() {
        return certPwd;
    }

    public boolean isVerifyCert() {
        return isVerifyCert;
    }

    public boolean isProxy() {
        return isProxy;
    }

    public int getProxyPort() {
        return proxyPort;
    }

    public String getProxyHostName() {
        return proxyHostName;
    }
}
